Skip to content

Conversation

@jelilat
Copy link
Contributor

@jelilat jelilat commented Jan 12, 2026

Closes #8616


PR-Codex overview

This PR introduces support for handling 402 payment-required headers in the thirdweb package, enhancing the payment-fetching functionality by decoding payment requirements from both headers and JSON bodies.

Detailed summary

  • Added safeBase64Decode function to packages/thirdweb/src/x402/encode.ts.
  • Updated wrapFetchWithPayment in packages/thirdweb/src/x402/fetchWithPayment.ts to:
    • Check for payment-required header.
    • Decode and parse payment requirements from the header if present.
    • Fallback to parsing from JSON body if the header is absent.
  • Added tests in packages/thirdweb/src/x402/fetchWithPayment.test.ts to verify:
    • Handling of non-402 responses.
    • Correct parsing of payment requirements from both header and body.
    • Preference for header values over body values when both are present.
    • Decoding of a raw base64 encoded payment-required header.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features

    • Enhanced payment handling: prefers decoding payment requirements from a base64-encoded response header in 402 flows, with fallback to body parsing, validation, and retry with payment headers.
    • Exposed base64 decode utility for use across the codebase.
  • Tests

    • Added comprehensive tests covering header/body parsing, decoded-header scenarios, chain selection, retry behavior with payment headers, and validation cases.
  • Documentation

    • Added a changeset noting support for x402 payment-required headers.

✏️ Tip: You can customize this high-level summary in your review settings.

@jelilat jelilat requested review from a team as code owners January 12, 2026 09:18
@changeset-bot
Copy link

changeset-bot bot commented Jan 12, 2026

🦋 Changeset detected

Latest commit: f6b8b8f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
thirdweb Patch
@thirdweb-dev/nebula Patch
@thirdweb-dev/wagmi-adapter Patch
wagmi-inapp Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Jan 12, 2026

@jelilat is attempting to deploy a commit to the thirdweb Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions bot added packages SDK Involves changes to the thirdweb SDK labels Jan 12, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

Walkthrough

Prefer decoding x402 payment requirements from a payment-required response header (via exported safeBase64Decode) and fall back to JSON body; normalize parsed payload and retain prior selection, chain-switching, and X-PAYMENT retry logic. Added comprehensive tests covering header/body parsing and retry flows.

Changes

Cohort / File(s) Summary
Encode Module Export
packages/thirdweb/src/x402/encode.ts
Made safeBase64Decode(data: string): string exported (visibility change only).
Payment Requirements Parsing & Retry
packages/thirdweb/src/x402/fetchWithPayment.ts
Handle 402 by checking payment-required header first, decode with safeBase64Decode, parse into { x402Version, accepts, error }, validate/normalize accepts (fallback to JSON body if header absent), then proceed with existing requirement selection, chain decision, create X-PAYMENT header, retry once, and clear cached signatures on second 402.
Tests: 402 Handling
packages/thirdweb/src/x402/fetchWithPayment.test.ts
Added extensive tests covering non-402 passthrough, header-based parsing (including base64 decoding), JSON-body fallback, preference when both header and body present, maxValue logic, chain switching, and verification that the retry includes X-PAYMENT.
Changeset
.changeset/social-ads-arrive.md
Added changeset noting patch release: "Support for x402 payment-required headers."

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant Decoder

    Client->>Server: fetch(request)
    Server-->>Client: 402 Payment Required (may include `payment-required` header or JSON body)

    alt Header present
        Client->>Decoder: read `payment-required` header
        Decoder->>Decoder: safeBase64Decode(header) -> parsedPayload
    else Header absent
        Client->>Client: await response.json() -> parsedPayload
    end

    Client->>Client: normalize parsedPayload -> {x402Version, accepts, error}
    Client->>Client: select requirement, possibly switch chain
    Client->>Client: create X-PAYMENT header
    Client->>Server: fetch(request + X-PAYMENT)
    Server-->>Client: 200 OK / success
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title '[SDK] Fix: Parse x402 payment header' is clear, concise, and directly describes the main change—adding support for parsing x402 payment headers in the SDK's payment-fetching functionality.
Description check ✅ Passed The description includes the linked issue reference, provides a detailed PR-Codex summary with specific changes and test coverage, explaining what was added and how to verify the fixes.
Linked Issues check ✅ Passed The pull request fully addresses all three coding objectives from issue #8616: checking response headers for x402 payment requirements first, falling back to JSON body parsing, and normalizing both paths before mapping to avoid runtime errors.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the objectives: exporting safeBase64Decode, implementing header-first parsing with body fallback in fetchWithPayment.ts, and adding comprehensive tests. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @packages/thirdweb/src/x402/fetchWithPayment.ts:
- Around line 83-108: The code assumes parsed.accepts/body.accepts is always an
array before calling .map(), causing runtime errors if missing or invalid;
update the payment-required header branch (paymentRequiredHeader -> decoded ->
parsed) and the fallback body branch (await response.json() -> body) to
defensively validate that accepts is an array (Array.isArray(parsed.accepts) /
Array.isArray(body.accepts)) before mapping: either default to an empty array or
throw a clear error via processLogger/throw with contextual info, then pass each
item through RequestedPaymentRequirementsSchema.parse; also ensure error
handling for JSON.parse/safeBase64Decode and response.json failures is
preserved.
🧹 Nitpick comments (3)
packages/thirdweb/src/x402/fetchWithPayment.ts (1)

85-96: Consider wrapping header decoding in try-catch for robustness.

If the payment-required header contains malformed base64 or invalid JSON, safeBase64Decode or JSON.parse will throw unhandled exceptions. Wrapping in try-catch with a descriptive error would improve debuggability.

♻️ Optional: Add error handling for malformed headers
    if (paymentRequiredHeader) {
+     let parsed: { x402Version: number; accepts: unknown[]; error?: string };
+     try {
        const decoded = safeBase64Decode(paymentRequiredHeader);
-       const parsed = JSON.parse(decoded) as {
-         x402Version: number;
-         accepts: unknown[];
-         error?: string;
-       };
+       parsed = JSON.parse(decoded);
+     } catch (e) {
+       throw new Error(
+         `Failed to parse payment-required header: ${e instanceof Error ? e.message : String(e)}`,
+       );
+     }
      x402Version = parsed.x402Version;
packages/thirdweb/src/x402/fetchWithPayment.test.ts (2)

180-210: Duplicate test case.

This test ("should parse payment requirements from payment-required header") is functionally identical to the test on lines 74-104 ("should parse payment requirements from payment-required header when present"). Both verify header parsing with the same assertions.

Consider removing one or differentiating them to test distinct edge cases.


19-59: Consider adding error scenario tests.

The test suite covers happy paths well. Consider adding tests for edge cases:

  • Malformed base64 in payment-required header
  • Missing or non-array accepts field
  • Wallet not connected (getAccount returns undefined)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0382d42 and b5b1a7b.

📒 Files selected for processing (3)
  • packages/thirdweb/src/x402/encode.ts
  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
  • packages/thirdweb/src/x402/fetchWithPayment.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each TypeScript file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • packages/thirdweb/src/x402/fetchWithPayment.ts
  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
  • packages/thirdweb/src/x402/encode.ts
packages/thirdweb/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/thirdweb/src/**/*.{ts,tsx}: Comment only ambiguous logic in SDK code; avoid restating TypeScript in prose
Load heavy dependencies inside async paths to keep initial bundle lean (e.g. const { jsPDF } = await import("jspdf");)

Lazy-load heavy dependencies inside async paths to keep the initial bundle lean (e.g., const { jsPDF } = await import('jspdf');)

Files:

  • packages/thirdweb/src/x402/fetchWithPayment.ts
  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
  • packages/thirdweb/src/x402/encode.ts
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • packages/thirdweb/src/x402/fetchWithPayment.ts
  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
  • packages/thirdweb/src/x402/encode.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • packages/thirdweb/src/x402/fetchWithPayment.ts
  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
  • packages/thirdweb/src/x402/encode.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx}: Place tests alongside code: foo.tsfoo.test.ts in the same directory
Use real function invocations with stub data in tests; avoid brittle mocks
Use Mock Service Worker (MSW) for fetch/HTTP call interception in tests
Keep tests deterministic and side-effect free
Use predefined test accounts from test/src/test-wallets.ts in tests
Use FORKED_ETHEREUM_CHAIN for mainnet interactions and ANVIL_CHAIN for isolated tests

**/*.test.{ts,tsx}: Co-locate tests with source files using the pattern foo.ts ↔ foo.test.ts
Use real function invocations with stub data in tests; avoid brittle mocks
For network interactions in tests, use Mock Service Worker (MSW) to intercept fetch/HTTP calls, mocking only scenarios that are hard to reproduce
Keep tests deterministic and side-effect free; Vitest is pre-configured

Files:

  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
🧬 Code graph analysis (1)
packages/thirdweb/src/x402/fetchWithPayment.test.ts (1)
packages/thirdweb/src/x402/encode.ts (1)
  • safeBase64Decode (69-77)
🔇 Additional comments (4)
packages/thirdweb/src/x402/encode.ts (1)

69-77: LGTM!

Exporting safeBase64Decode enables header-based payment requirement parsing in fetchWithPayment.ts. The dual-environment implementation (browser atob / Node.js Buffer) is appropriate.

packages/thirdweb/src/x402/fetchWithPayment.test.ts (3)

61-72: LGTM!

Good coverage of the non-402 passthrough case. The test verifies the response is returned unchanged and fetch is only called once.


131-178: LGTM!

Clever use of maxValue constraint to verify header takes precedence over body. This effectively proves the header path is used when both are present.


212-263: LGTM!

Good integration test with a real base64-encoded payload. Testing with chain 8453 (Base) ensures the chain matching logic is exercised.

Copy link
Member

@joaquim-verges joaquim-verges left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me, thank you for contributing @jelilat !

@jelilat
Copy link
Contributor Author

jelilat commented Jan 12, 2026

looks good to me, thank you for contributing @jelilat !

Thanks @joaquim-verges

Quick question. Is coderabit going to make this change, or it's better if I do it

@joaquim-verges
Copy link
Member

did the change for you @jelilat and added a changeset

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @packages/thirdweb/src/x402/fetchWithPayment.ts:
- Around line 85-103: Wrap the existing header parsing (the block that reads
paymentRequiredHeader, calls safeBase64Decode, JSON.parse, and maps to
RequestedPaymentRequirementsSchema.parse) in a try-catch so malformed
base64/JSON or invalid schema does not throw and instead leaves
parsedPaymentRequirements undefined; initialize parsedPaymentRequirements (and
x402Version/error as needed) before the header block, catch any errors from
safeBase64Decode/JSON.parse/RequestedPaymentRequirementsSchema.parse and
swallow/log them as non-fatal, then only proceed to parse the body if
parsedPaymentRequirements is still undefined, and finally throw only if both
header and body parsing fail.
🧹 Nitpick comments (2)
packages/thirdweb/src/x402/fetchWithPayment.test.ts (2)

206-243: Duplicate test case.

This test is nearly identical to the test at lines 78-115 ("should parse payment requirements from payment-required header when present"). Consider removing this duplicate to reduce maintenance burden.


19-59: Consider adding error case tests.

The test suite covers happy paths well but lacks coverage for error scenarios:

  • Malformed base64 in payment-required header
  • Invalid JSON after base64 decode
  • Missing or non-array accepts field
  • Zod schema validation failures

These would help verify error handling behavior and catch regressions.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 642478a and f6b8b8f.

📒 Files selected for processing (3)
  • .changeset/social-ads-arrive.md
  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
  • packages/thirdweb/src/x402/fetchWithPayment.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each TypeScript file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
  • packages/thirdweb/src/x402/fetchWithPayment.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx}: Place tests alongside code: foo.tsfoo.test.ts in the same directory
Use real function invocations with stub data in tests; avoid brittle mocks
Use Mock Service Worker (MSW) for fetch/HTTP call interception in tests
Keep tests deterministic and side-effect free
Use predefined test accounts from test/src/test-wallets.ts in tests
Use FORKED_ETHEREUM_CHAIN for mainnet interactions and ANVIL_CHAIN for isolated tests

**/*.test.{ts,tsx}: Co-locate tests with source files using the pattern foo.ts ↔ foo.test.ts
Use real function invocations with stub data in tests; avoid brittle mocks
For network interactions in tests, use Mock Service Worker (MSW) to intercept fetch/HTTP calls, mocking only scenarios that are hard to reproduce
Keep tests deterministic and side-effect free; Vitest is pre-configured

Files:

  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
packages/thirdweb/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/thirdweb/src/**/*.{ts,tsx}: Comment only ambiguous logic in SDK code; avoid restating TypeScript in prose
Load heavy dependencies inside async paths to keep initial bundle lean (e.g. const { jsPDF } = await import("jspdf");)

Lazy-load heavy dependencies inside async paths to keep the initial bundle lean (e.g., const { jsPDF } = await import('jspdf');)

Files:

  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
  • packages/thirdweb/src/x402/fetchWithPayment.ts
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
  • packages/thirdweb/src/x402/fetchWithPayment.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • packages/thirdweb/src/x402/fetchWithPayment.test.ts
  • packages/thirdweb/src/x402/fetchWithPayment.ts
🧬 Code graph analysis (2)
packages/thirdweb/src/x402/fetchWithPayment.test.ts (1)
packages/thirdweb/src/x402/encode.ts (1)
  • safeBase64Decode (69-77)
packages/thirdweb/src/x402/fetchWithPayment.ts (3)
packages/thirdweb/src/x402/types.ts (1)
  • x402Version (14-14)
packages/thirdweb/src/x402/schemas.ts (2)
  • RequestedPaymentRequirements (40-42)
  • RequestedPaymentRequirementsSchema (34-38)
packages/thirdweb/src/x402/encode.ts (1)
  • safeBase64Decode (69-77)
🔇 Additional comments (9)
.changeset/social-ads-arrive.md (1)

1-5: LGTM!

Changeset is well-formed with appropriate patch-level bump for this backward-compatible bug fix.

packages/thirdweb/src/x402/fetchWithPayment.ts (3)

7-7: LGTM!

Import correctly references the newly exported safeBase64Decode function.


104-122: LGTM!

Body parsing fallback logic correctly mirrors the header path and maintains consistent validation.


79-81: LGTM!

Good approach normalizing the parsed data into shared variables before the common processing logic.

packages/thirdweb/src/x402/fetchWithPayment.test.ts (5)

5-17: LGTM!

Mock setup correctly isolates external dependencies without exposing brittle implementation details.


19-59: LGTM!

Well-structured test fixtures with appropriate minimal mocks that ensure test isolation via beforeEach.


61-76: LGTM!

Good baseline test verifying non-402 responses pass through unmodified.


117-204: LGTM!

Good test coverage for body fallback and header preference. The maxValue-based verification at lines 188-198 is a clever technique to confirm header data takes precedence.


245-303: LGTM!

Excellent end-to-end test with a realistic base64-encoded header, verifying both the decoding utility and full payment flow with chain matching.

Comment on lines +85 to +103
if (paymentRequiredHeader) {
const decoded = safeBase64Decode(paymentRequiredHeader);
const parsed = JSON.parse(decoded) as {
x402Version: number;
accepts: unknown[];
error?: string;
};

if (!Array.isArray(parsed.accepts)) {
throw new Error(
`402 response has no usable x402 payment requirements. ${parsed.error ?? ""}`,
);
}

x402Version = parsed.x402Version;
parsedPaymentRequirements = parsed.accepts.map((x) =>
RequestedPaymentRequirementsSchema.parse(x),
);
error = parsed.error;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing error handling for malformed header parsing.

If the payment-required header contains invalid base64 or malformed JSON, safeBase64Decode or JSON.parse will throw, preventing fallback to the body path. Consider wrapping header parsing in try-catch to gracefully fall back to body parsing when the header is present but malformed.

🛠️ Suggested fix
     // Check payment-required header first before falling back to JSON body
     const paymentRequiredHeader = response.headers.get("payment-required");
     if (paymentRequiredHeader) {
+      try {
         const decoded = safeBase64Decode(paymentRequiredHeader);
         const parsed = JSON.parse(decoded) as {
           x402Version: number;
           accepts: unknown[];
           error?: string;
         };
 
         if (!Array.isArray(parsed.accepts)) {
           throw new Error(
             `402 response has no usable x402 payment requirements. ${parsed.error ?? ""}`,
           );
         }
 
         x402Version = parsed.x402Version;
         parsedPaymentRequirements = parsed.accepts.map((x) =>
           RequestedPaymentRequirementsSchema.parse(x),
         );
         error = parsed.error;
-    } else {
+      } catch (headerError) {
+        // Header parsing failed, fall through to body parsing
+      }
+    }
+
+    // Fall back to body if header was absent or parsing failed
+    if (!parsedPaymentRequirements) {
       const body = (await response.json()) as {
         x402Version: number;
         accepts: unknown[];
         error?: string;
       };
       // ... rest of body parsing
     }

Note: This would require initializing parsedPaymentRequirements as undefined initially and checking it before body parsing.

🤖 Prompt for AI Agents
In @packages/thirdweb/src/x402/fetchWithPayment.ts around lines 85 - 103, Wrap
the existing header parsing (the block that reads paymentRequiredHeader, calls
safeBase64Decode, JSON.parse, and maps to
RequestedPaymentRequirementsSchema.parse) in a try-catch so malformed
base64/JSON or invalid schema does not throw and instead leaves
parsedPaymentRequirements undefined; initialize parsedPaymentRequirements (and
x402Version/error as needed) before the header block, catch any errors from
safeBase64Decode/JSON.parse/RequestedPaymentRequirementsSchema.parse and
swallow/log them as non-fatal, then only proceed to parse the body if
parsedPaymentRequirements is still undefined, and finally throw only if both
header and body parsing fail.

@joaquim-verges joaquim-verges merged commit 5766c90 into thirdweb-dev:main Jan 12, 2026
14 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

packages SDK Involves changes to the thirdweb SDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

x402: Payment requirements parsing assumes JSON body, but spec allows headers

2 participants